package com.auroramanito.uaa.config;

import com.auroramanito.uaa.security.auth.ldap.LDAPMultiAuthenticationProvider;
import com.auroramanito.uaa.security.auth.ldap.LDAPUserRepo;
import com.auroramanito.uaa.security.filter.RestAuthenticationFilter;
import com.auroramanito.uaa.security.jwt.JwtFilter;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.MessageDigestPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport;

import javax.sql.DataSource;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
@EnableWebSecurity(debug = true)
@RequiredArgsConstructor
@Import(SecurityProblemSupport.class)
@Order(99)
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final ObjectMapper objectMapper;
    private final SecurityProblemSupport securityProblemSupport;
    private final DataSource dataSource;
    private final UserDetailsService userDetailsService;
    private final UserDetailsPasswordService userDetailsPasswordService;

    //LDAP  AuthProvider配置
    private final LDAPUserRepo ldapUserRepo;
    private final JwtFilter jwtFilter;

    private final Environment environment;


    /**
     *  WebSecurity 和 HTTPSecurity 区别
     *  HTTP Security会启动过滤器链
     * */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //REST 登录
        http.requestMatchers(req->req.mvcMatchers("/authorize/**","/admin/**","/api/**"))
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .exceptionHandling(exp -> exp
                .authenticationEntryPoint(securityProblemSupport)
                .accessDeniedHandler(securityProblemSupport))
                .cors(cors-> cors.configurationSource(configurationSource()))
                .authorizeRequests(req -> req
                    .antMatchers("/authorize/**").permitAll()
                    .antMatchers("/admin/**").hasRole("ADMIN")
                    .antMatchers("/api/users/manager").hasRole("MANAGER")
                    .antMatchers("/api/user/by-email/{email}").hasRole("user")
                    .antMatchers("/api/users/{username}").access("hasRole('ADMIN') or @userService.isValidUser(authentication,#username)")
                    .antMatchers("/api/**").hasRole("USER")
                    .anyRequest().authenticated())
                //UsernamePasswordAuthenticationFilter
                //·如果在请求参数或者POST的Request Body 中找到用户名/密码，则尝试用这些值对用户进行身份验证。
                .addFilterAt(restAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(jwtFilter,UsernamePasswordAuthenticationFilter.class)
                .csrf(csrf -> csrf.disable())
                .formLogin(AbstractHttpConfigurer::disable)
                //httpBasic是认证头,可以采用默认的方式 可以携带Authorization： Basic user 12345678 的认证头
                //.httpBasic(AbstractHttpConfigurer::disable);
                .httpBasic(Customizer.withDefaults());
    }

    /**
     * restAuthenticationFilter 继承 UserNamePasswordAuthenticationFilter
     * 实现request Body 参数的密码验证
     * POST http://localhost:8080/authorize/token
     * Content-Type:application/json; charset=utf-8
     *
     * {
     *   "username": "user",
     *   "password": "12345678"
     * }
     */
    private RestAuthenticationFilter restAuthenticationFilter() throws Exception {
        RestAuthenticationFilter filter = new RestAuthenticationFilter(objectMapper);
        filter.setAuthenticationSuccessHandler(getAuthenticationSuccessHandler());
        filter.setAuthenticationFailureHandler(getAuthenticationFailureHandler());
        filter.setAuthenticationManager(authenticationManager());
        filter.setFilterProcessesUrl("/authorize/login");
        return filter;
    }
    private AuthenticationSuccessHandler getAuthenticationSuccessHandler() {
        return (req, res, auth) -> {
            ObjectMapper objectMapper = new ObjectMapper();
            res.setStatus(HttpStatus.OK.value());
            res.getWriter().println(objectMapper.writeValueAsString(auth));
            log.debug("认证成功");
        };
    }
    private AuthenticationFailureHandler getAuthenticationFailureHandler() {
        return (req, res, exp) -> {
            val objectMapper = new ObjectMapper();
            res.setStatus(HttpStatus.UNAUTHORIZED.value());
            res.setContentType(MediaType.APPLICATION_JSON_VALUE);
            res.setCharacterEncoding("UTF-8");
            val errData = Map.of(
                    "title", "认证失败",
                    "details", exp.getMessage()
            );
            res.getWriter().println(objectMapper.writeValueAsString(errData));
        };
    }

    /**
     * 相当于通过YML配置用户授权
     * */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        auth.inMemoryAuthentication()
//                .passwordEncoder(passwordEncoder())
//                .withUser("user1")
//                .password(passwordEncoder().encode("12345678"))
//                .password(new MessageDigestPasswordEncoder("SHA-1").encode("123456"))
//                .roles("USER","ADMIN");
//        auth.jdbcAuthentication()
//                .dataSource(dataSource)
//                .passwordEncoder(passwordEncoder());
//                .withUser("user")
//                .password(passwordEncoder().encode("123456"))
//                .roles("USER","ADMIN");


//        auth.userDetailsService(userDetailsService)
//                .passwordEncoder(passwordEncoder());

        auth.authenticationProvider(daoAuthenticationProvider());
        auth.authenticationProvider(ldapMultiAuthenticationProvider());
    }

//    @Bean
//    PasswordEncoder passwordEncoder(){
//        return new BCryptPasswordEncoder();
//    }

    @Bean
    PasswordEncoder passwordEncoder(){
        //默认编码算法的Id, 新的密码编码都会使用这个id对应的编码器
        val idForEncode = "bcrypt";
        //要支持的多种编码器
        val encoders = Map.of(idForEncode,new BCryptPasswordEncoder(),
                "SHA-1",new MessageDigestPasswordEncoder("SHA-1")
        );
        return new DelegatingPasswordEncoder(idForEncode,encoders);
    }
    /**
     *
     * 不需要启动过滤链
     * Security filter chain: [] empty (bypassed by security='none')
     * */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().mvcMatchers("/public/**","/error");
    }

    /**
     *
     * */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    //JDBC provider
    @Bean
    DaoAuthenticationProvider daoAuthenticationProvider(){
        val daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService);
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        daoAuthenticationProvider.setUserDetailsPasswordService(userDetailsPasswordService);
        return daoAuthenticationProvider;
    }

    //LDAP provider
    @Bean
    LDAPMultiAuthenticationProvider ldapMultiAuthenticationProvider(){
        val ldapMultiAuthenticationProvider = new LDAPMultiAuthenticationProvider(ldapUserRepo);
        return ldapMultiAuthenticationProvider;
    }

    /**
     * Cors 跨域解决方案
     * */
    @Bean
    CorsConfigurationSource configurationSource(){
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        if(environment.acceptsProfiles(Profiles.of("dev"))){
            //开发环境下允许跨域访问主机
            corsConfiguration.addAllowedOrigin("http://localhost:4001");
        }else {
            corsConfiguration.addAllowedOrigin("http://uaa.imooc.com");
        }
        corsConfiguration.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS"));
        corsConfiguration.setAllowedHeaders(Collections.singletonList("*"));
        //开放跨域响应头
        corsConfiguration.addExposedHeader("X-Authenticate");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**",corsConfiguration);
        return source;
    }

    /**
     * 7.3 角色
     * */
    @Bean
    public RoleHierarchy roleHierarchy(){
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_MANAGER\nROLE_MANAGER > ROLE_USER");
        return roleHierarchy;
    }
}

